/*****************************************************\
* Copyright (c) 2020 The game2d Authors. All rights reserved.
  Use of this source code is governed by a BSD-style license that can be
  found in the LICENSE file.

* Author:         dzlua - https://gitee.com/dzlua/game2d.git
* Email:          505544956@qq.com
* FileName:       core/builder.cc
* Create Time:    2020/03/21 18:37:24
* Description:    /
\****************************************************/
#include "builder.h"

#include "tinyxml2.h"

#include "core/sprite.h"
#include "core/tilemap.h"
#include "ctrls/ui.h"
#include "ctrls/layout.h"

namespace g2d {
  GameObject* create_internal_object(const char *type, bool load_res_on_enter) {
    if (strcmp(type, "ui") == 0) return new ctrl::UserInterface(load_res_on_enter);
    else if (strcmp(type, "control") == 0) return new ctrl::Control(load_res_on_enter);
    else if (strcmp(type, "label") == 0) return new ctrl::Label(load_res_on_enter);
    else if (strcmp(type, "fps") == 0) return new ctrl::FpsLabel(load_res_on_enter);
    else if (strcmp(type, "button") == 0) return new ctrl::Button(load_res_on_enter);
    else if (strcmp(type, "layout") == 0) return new ctrl::Layout(load_res_on_enter);
    else if (strcmp(type, "horlayout") == 0) return new ctrl::HorLayout(load_res_on_enter);
    else if (strcmp(type, "verlayout") == 0) return new ctrl::VerLayout(load_res_on_enter);
    else if (strcmp(type, "tilelayout") == 0) return new ctrl::TileLayout(load_res_on_enter);
    else if (strcmp(type, "tablayout") == 0) return new ctrl::TabLayout(load_res_on_enter);

    else if (strcmp(type, "sprite") == 0) return new Sprite(load_res_on_enter);
    else if (strcmp(type, "anisprite") == 0) return new AniSprite(load_res_on_enter);
    else if (strcmp(type, "tilemap") == 0) return new TileMap(load_res_on_enter);
    else return 0;
  }

  Builder::Builder(Game2D *g2d) : g2d_(g2d) {}
  Builder::~Builder() {}
  Result Builder::load_res_file(const char *xml) {
    if (!xml || !*xml) return Result::E_InvalidParam;
    tinyxml2::XMLDocument doc;
    doc.LoadFile(xml);
    return parse_res(&doc);
  }
  Result Builder::load_res_buffer(const char *xml, Uint32 len) {
    if (!xml || !*xml || len == 0) return Result::E_InvalidParam;
    tinyxml2::XMLDocument doc;
    doc.Parse(xml, len);
    return parse_res(&doc);
  }
  Result Builder::load_scene_file(const char *xml) {
    if (!xml || !*xml) return Result::E_InvalidParam;
    tinyxml2::XMLDocument doc;
    doc.LoadFile(xml);
    return parse_scene(&doc);
  }
  Result Builder::load_scene_buffer(const char *xml, Uint32 len) {
    if (!xml || !*xml || len == 0) return Result::E_InvalidParam;
    tinyxml2::XMLDocument doc;
    doc.Parse(xml, len);
    return parse_scene(&doc);
  }
  Result Builder::parse_res(tinyxml2::XMLDocument *doc) {
    if (!doc) return Result::E_InvalidParam;
    if (doc->Error()) return Result::E_XmlFail;
    auto root = doc->RootElement();
    if (!root || strcmp(root->Name(), "game2d") != 0)
      return Result::E_XmlFail;

    const char *name = 0;
    Result rst = Result::S_OK;
    auto child = root->FirstChildElement();
    while (child) {
      name = child->Name();
      if (strcmp(name, "res") == 0) {
        rst = parse_res(child, 0);
      } else if (strcmp(name, "scene") == 0) {
        auto src = child->Attribute("src");
        if (src) {
          rst = load_scene_file(src);
        } else {
          rst = parse_scene(child);
        }
      }
      if (Failed(rst)) break;
      child = child->NextSiblingElement();
    }

    if (Succeeded(rst))
      rst = g2d_->change_scene(root->Attribute("scene"));
    if (Succeeded(rst)) {
      auto fps = root->UnsignedAttribute("fps");
      g2d_->set_fps(fps);
    }
    if (Succeeded(rst)) {
      auto screen = root->Attribute("screen");
      if (screen) {
        if (strcmp(screen, "real") == 0) rst = g2d_->enter_fullscreen(true);
        else if (strcmp(screen, "fake") == 0) rst = g2d_->enter_fullscreen(false);
        else rst = g2d_->exit_fullscreen();
      }
    }
    if (Succeeded(rst)) {
      auto size = utils::parse_sizei(root->Attribute("size"));
      if (size.w > 0 && size.h > 0) {
        rst = g2d_->set_window_size(size.w, size.h);
      }
    }
    return rst;
  }
  Result Builder::parse_scene(tinyxml2::XMLDocument *doc) {
    if (!doc) return Result::E_InvalidParam;
    if (doc->Error()) return Result::E_XmlFail;
    auto root = doc->RootElement();
    if (!root || strcmp(root->Name(), "scene") != 0)
      return Result::E_XmlFail;
    return parse_scene(root);
  }
  Result Builder::parse_res(tinyxml2::XMLElement *ele, Scene *scene) {
    if (!ele) return Result::E_InvalidParam;
    auto &font = g2d_->font();
    auto &audio = g2d_->audio();
    auto &texture = g2d_->texture();

    const char *name = 0;
    Result rst = Result::S_OK;
    auto child = ele->FirstChildElement();
    while (child) {
      name = child->Name();
      if (strcmp(name, "font") == 0) {
        rst = parse_font(child, font);
      } else if (strcmp(name, "audio") == 0) {
        rst = parse_audio(child, audio);
      } else if (strcmp(name, "texture") == 0) {
        rst = parse_texture(child, texture);
      } else if (strcmp(name, "image") == 0) {
        rst = parse_image(child);
      }
      if (Failed(rst)) break;
      child = child->NextSiblingElement();
    }
    return rst;
  }
  Result Builder::parse_scene(tinyxml2::XMLElement *ele) {
    if (!ele) return Result::E_InvalidParam;
    auto scene = g2d_->get_scene(ele->Attribute("id"), true);
    if (!scene) return Result::E_XmlFail;
    auto s = ele->Attribute("loading");
    bool load_res_on_enter = s ? (strcmp(s, "init") != 0) : false;

    const char *name = 0;
    Result rst = Result::S_OK;
    auto child = ele->FirstChildElement();
    while (child) {
      name = child->Name();
      if (strcmp(name, "res") == 0) {
        rst = parse_res(child, scene);
      } else if (strcmp(name, "layer") == 0) {
        rst = parse_layer(child, scene, load_res_on_enter);
      }
      if (Failed(rst)) break;
      child = child->NextSiblingElement();
    }
    return rst;
  }
  Result Builder::parse_layer(tinyxml2::XMLElement *ele, Scene *scene, bool load_res_on_enter) {
    if (!ele || !scene) return Result::E_InvalidParam;
    auto id = ele->Attribute("id");
    if (!id) id = "";
    auto layer = scene->get(id, true);
    if (!layer) return Result::E_XmlFail;

    const char *name = 0;
    Result rst = Result::S_OK;
    auto child = ele->FirstChildElement();
    while (child) {
      name = child->Name();
      if (strcmp(name, "ui") == 0) {
        rst = parse_ui(child, layer, load_res_on_enter);
      } else if (strcmp(name, "sprite") == 0) {
        rst = parse_sprite(child, layer, name, load_res_on_enter);
      } else if (strcmp(name, "anisprite") == 0) {
        rst = parse_sprite(child, layer, name, load_res_on_enter);
      } else if (strcmp(name, "tilemap") == 0) {
        rst = parse_sprite(child, layer, name, load_res_on_enter);
      }
      if (Failed(rst)) break;
      child = child->NextSiblingElement();
    }
    return rst;
  }
  Result Builder::parse_sprite(tinyxml2::XMLElement *ele, Layer *layer, const char *type, bool load_res_on_enter) {
    if (!ele || !layer) return Result::E_InvalidParam;
    auto id = ele->Attribute("id");
    if (!id) id = "";
    auto obj = layer->create_gobj(type, id, G2D_INVALDATE_INDEX, load_res_on_enter);
    if (!obj) return Result::E_XmlFail;
    auto sprite = dynamic_cast<Sprite*>(obj);
    if (!sprite) return Result::E_XmlFail;
    for (auto attr = ele->FirstAttribute(); attr; attr = attr->Next()) {
      sprite->set_attribute(attr->Name(), attr->Value());
    }
    sprite->doinit();
    return Result::S_OK;
  }
  Result Builder::parse_ui(tinyxml2::XMLElement *ele, Layer *layer, bool load_res_on_enter) {
    if (!ele || !layer) return Result::E_InvalidParam;
    auto id = ele->Attribute("id");
    if (!id) id = "";
    auto ui = layer->create_gobj("ui", id, G2D_INVALDATE_INDEX, load_res_on_enter);
    if (!ui) return Result::E_XmlFail;
    return parse_ctrls(ele
        , dynamic_cast<ctrl::UserInterface*>(ui)
        , 0
        , load_res_on_enter);
  }
  Result Builder::parse_ctrls(tinyxml2::XMLElement *ele, ctrl::UserInterface *ui, ctrl::Control *parent, bool load_res_on_enter) {
    if (!ele || !ui) return Result::E_InvalidParam;
    ctrl::Control *ctrl = 0;
    const char *ctrl_type = 0;
    const char *name = 0;
    Result rst = Result::S_OK;
    auto child = ele->FirstChildElement();
    while (child) {
      name = child->Name();
      if (strcmp(name, "layout") == 0) {
        auto type = child->Attribute("type");
        if (type) {
          if (strcmp(type, "ver") == 0) ctrl_type = "verlayout";
          else if (strcmp(type, "hor") == 0) ctrl_type = "horlayout";
          else if (strcmp(type, "tile") == 0) ctrl_type = "tilelayout";
          else if (strcmp(type, "tab") == 0) ctrl_type = "tablayout";
          else ctrl_type = "layout";
        } else ctrl_type = "layout";
      } else ctrl_type = name;
      
      auto id = ele->Attribute("id");
      if (!id) id = "";

      if (!parent) {
        auto obj = ui->create_gobj(ctrl_type, id, G2D_INVALDATE_INDEX, load_res_on_enter);
        ctrl = dynamic_cast<ctrl::Control*>(obj);
      } else {
        auto obj = parent->create_gobj(ctrl_type, id, G2D_INVALDATE_INDEX, load_res_on_enter);
        ctrl = dynamic_cast<ctrl::Control*>(obj);
      }
      if (ctrl == 0) rst = Result::E_XmlFail;
      if (Failed(rst)) break;
      
      for (auto attr = child->FirstAttribute(); attr; attr = attr->Next()) {
        ctrl->set_attribute(attr->Name(), attr->Value());
      }
      ctrl->doinit();

      if (!parent) {
        ui->set_root(dynamic_cast<ctrl::Layout*>(ctrl));
        return parse_ctrls(child, ui, ctrl, load_res_on_enter);
      } else {
        rst = parse_ctrls(child, ui, ctrl, load_res_on_enter);
      }

      child = child->NextSiblingElement();
    }
    return rst;
  }
  
  Result Builder::parse_font(tinyxml2::XMLElement *ele, Font &font) {
    if (!ele) return Result::E_InvalidParam;

    const char *id = 0;
    const char *file = 0;
    Uint32 size = 20;
    bool bold = false;
    bool italic = false;
    bool underline = false;
    bool strikethrough = false;

    const char *name = 0;
    for (auto attr = ele->FirstAttribute(); attr; attr = attr->Next()) {
      name = attr->Name();
      if (strcmp(name, "id") == 0) id = attr->Value();
      else if (strcmp(name, "file") == 0) file = attr->Value();
      else if (strcmp(name, "size") == 0) size = attr->UnsignedValue();
      else if (strcmp(name, "bold") == 0) bold = attr->BoolValue();
      else if (strcmp(name, "italic") == 0) italic = attr->BoolValue();
      else if (strcmp(name, "underline") == 0) underline = attr->BoolValue();
      else if (strcmp(name, "strikethrough") == 0) strikethrough = attr->BoolValue();
    }
    return font.load(id, file, size, bold, italic, underline, strikethrough);
  }
  Result Builder::parse_audio(tinyxml2::XMLElement *ele, Audio &audio) {
    if (!ele) return Result::E_InvalidParam;

    const char *id = 0;
    const char *file = 0;
    const char *type = 0;

    const char *name = 0;
    for (auto attr = ele->FirstAttribute(); attr; attr = attr->Next()) {
      name = attr->Name();
      if (strcmp(name, "id") == 0) id = attr->Value();
      else if (strcmp(name, "file") == 0) file = attr->Value();
      else if (strcmp(name, "type") == 0) type = attr->Value();
    }
    return type == "music"
        ? audio.load_music_file(id, file)
        : audio.load_sfx_file(id, file);
  }
  Result Builder::parse_texture(tinyxml2::XMLElement *ele, Texture &texture) {
    if (!ele) return Result::E_InvalidParam;

    const char *id = 0;
    const char *file = 0;

    const char *name = 0;
    for (auto attr = ele->FirstAttribute(); attr; attr = attr->Next()) {
      name = attr->Name();
      if (strcmp(name, "id") == 0) id = attr->Value();
      else if (strcmp(name, "file") == 0) file = attr->Value();
    }
    return texture.load_file(id, file);
  }
  Result Builder::parse_image(tinyxml2::XMLElement *ele) {
    if (!ele) return Result::E_InvalidParam;
    return Result::E_NotImplemented;
  }
} // end namespace g2d